package 多线程3;
//重点研究一下BlockingQueue代码内部是如何实现的,尤其是线程安全和阻塞等待,分别是咋搞的
//数组实现队列,其实就是循环队列 --
//循环队列,如何区分队列满队列空? --
public class TestDemo4 {
    //弄内部类是为了和标准库里边的BlockingQueue作区分
    static class BlockingQueue{
        //队列的最大容量,扩容暂时不考虑
        private static int[] items = new int[1000];
        private int head = 0;
        private int tail = 0;
        private int size = 0;
        private Object locker = new Object();
        //put用来入队列
        public void put(int item) throws InterruptedException {
            //可以用this,也可以设置一个单独的对象来作为锁对象(只要能够保证加锁的时候是针对同一个对象进行加锁,就没问题)
            synchronized (locker){
                //此处的条件最好是写成while而不是if
                //如果是有多个线程阻塞等待的时候,万一同时唤醒了多个线程,
                //就有可能出现,第一个线程放入元素之后(就满了),第二个线程想放,就又满了的情况
                //虽然当前take的代码中使用的是notify,一次只唤醒一个等待的线程,用if也不算错.
                //但是,使用while还是更稳健一些.
                //使用while的意思就是,保证wait被唤醒的还是能够再确认一次队列确实不满
//               --
//                if(size == items.length){
                while(size == items.length){
                    //队列已满
                    //对于阻塞队列来说,就要进行阻塞
//                    return;
                    locker.wait();//如果这边的队列是满的,那么就进行等待
                }
                items[tail++] = item;
                //如果到达末尾,就回到起始位置
                if(tail >= items.length){
                    tail = 0;
                }
                size++;
                locker.notify();
            }
        }
        //take用来出队列
        public int take() throws InterruptedException {
            int ret = 0;
            synchronized(locker){
                //和put中的一样,再次确认队列不为空
                //使用while就是为了让wait被唤醒之后,再次确认下条件是否成立
                while(this.size == 0){
                    //对于阻塞队列来说,如果队列为空,再尝试取元素,就要阻塞
//                    return 0;
                    locker.wait();
                }
                ret = this.items[this.head++];
                if(this.head >= items.length){
                    this.head = 0;
                }
                size--;
                //此处的notify用来唤醒put中的wait
                locker.notify();
            }
            return ret;
        }
    }

    /**
     * put和take都可能会出现阻塞的情况(wait)
     * 由于这两个代码中的阻塞条件是对立的,因此这两边的wait不会同时触发.
     * put来唤醒take的阻塞,put操作就破坏了take的阻塞条件
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
    BlockingQueue queue = new BlockingQueue();
    //消费者线程
        Thread consumer = new Thread(){
            @Override
            public void run() {
                while(true){
                    try {
                        int elem = queue.take();
                        System.out.println("消费元素:"+elem);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        consumer.start();

        Thread producer = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    try {
                        queue.put(i);
                        System.out.println("生产元素:"+i);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        producer.start();

        consumer.join();
        producer.join();
    }
}
